using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;

namespace VisualPureBasicIDE.FormDesigner.Implementations
{
    /// IMenuCommandService implementation.
    public class MenuCommandService : IMenuCommandService
    {
        // the host
        // commandId-command mapping
        private readonly IDictionary _commands;
        // menuItem-verb mapping
        // we use the same context menu over-and-over
        private readonly ContextMenu _contextMenu;
        private readonly DesignerVerbCollection _globalVerbs;
        private readonly IDesignerHost _host;
        // we keep the _lastSelectedComponent around 
        private IComponent _lastSelectedComponent;
        private readonly IDictionary _menuItemVerb;

        public MenuCommandService(IDesignerHost host)
        {
            _host = host;
            _commands = new Hashtable();
            _globalVerbs = new DesignerVerbCollection();
            _menuItemVerb = new Hashtable();
            _contextMenu = new ContextMenu();
            _lastSelectedComponent = null;
        }

        #region Implementation of IMenuCommandService

        /// called to add a MenuCommand
        public void AddCommand(MenuCommand command)
        {
            if (command == null)
            {
                throw new ArgumentException("command");
            }
            // don't add commands twice
            if (FindCommand(command.CommandID) == null)
            {
                _commands.Add(command.CommandID, command);
            }
        }

        /// called to remove a MenuCommand
        public void RemoveCommand(MenuCommand command)
        {
            if (command == null)
            {
                throw new ArgumentException("command");
            }
            _commands.Remove(command.CommandID);
        }

        /// called when to add a global verb
        public void AddVerb(DesignerVerb verb)
        {
            if (verb == null)
            {
                throw new ArgumentException("verb");
            }
            _globalVerbs.Add(verb);
            // create a menu item for the verb and add it to the context menu
            var menuItem = new MenuItem(verb.Text);
            menuItem.Click += MenuItemClickHandler;
            _menuItemVerb.Add(menuItem, verb);
            _contextMenu.MenuItems.Add(menuItem);
        }

        /// called to remove global verb
        public void RemoveVerb(DesignerVerb verb)
        {
            if (verb == null)
            {
                throw new ArgumentException("verb");
            }

            _globalVerbs.Remove(verb);

            // find the menu item associated with the verb
            MenuItem associatedMenuItem = null;
            foreach (DictionaryEntry de in _menuItemVerb)
            {
                if (de.Value == verb)
                {
                    associatedMenuItem = de.Key as MenuItem;
                    break;
                }
            }
            // if we found the verb's menu item, remove it
            if (associatedMenuItem != null)
            {
                _menuItemVerb.Remove(associatedMenuItem);
            }
            // remove the verb from the context menu too
            if (associatedMenuItem != null) _contextMenu.MenuItems.Remove(associatedMenuItem);
        }

        /// returns the MenuCommand associated with the commandId.
        public MenuCommand FindCommand(CommandID commandID)
        {
            return _commands[commandID] as MenuCommand;
        }

        /// called to invoke a command
        public bool GlobalInvoke(CommandID commandID)
        {
            var result = false;
            var command = FindCommand(commandID);
            if (command != null)
            {
                command.Invoke();
                result = true;
            }
            return result;
        }

        /// called to show the context menu for the selected component.
        public void ShowContextMenu(CommandID menuID, int x, int y)
        {
            var selectionService = _host.GetService(typeof (ISelectionService)) as ISelectionService;
            
            // get the primary component
            if (selectionService != null)
            {
                var primarySelection = selectionService.PrimarySelection as IComponent;
                // if the he clicked on the same component again then just show the context
                // menu. otherwise, we have to throw away the previous
                // set of local menu items and create new ones for the newly
                // selected component
                if (_lastSelectedComponent != primarySelection)
                {
                    // remove all non-global menu items from the context menu
                    ResetContextMenu();
                    // get the designer
                    var designer = _host.GetDesigner(primarySelection);
                    // not all controls need a desinger
                    if (designer != null)
                    {
                        // get designer's verbs
                        var verbs = designer.Verbs;
                        foreach (DesignerVerb verb in verbs)
                        {
                            // add new menu items to the context menu
                            CreateAndAddLocalVerb(verb);
                        }
                    }
                }
                // we only show designer context menus for controls
                if (primarySelection is Control)
                {
                    var comp = primarySelection as Control;
                    var pt = comp.PointToScreen(new Point(0, 0));
                    _contextMenu.Show(comp, new Point(x - pt.X, y - pt.Y));
                }
                // keep the selected component for next time
                _lastSelectedComponent = primarySelection;
            }
        }

        /// returns the the current designer verbs
        public DesignerVerbCollection Verbs
        {
            get
            {
                // create a new collection
                var availableVerbs = new DesignerVerbCollection();
                // add the global verbs
                if (_globalVerbs != null && _globalVerbs.Count > 0)
                {
                    availableVerbs.AddRange(_globalVerbs);
                }
                // now add the local verbs
                var selectionService = _host.GetService(typeof (ISelectionService)) as ISelectionService;
                if (selectionService != null)
                {
                    var primaryComponent = selectionService.PrimarySelection as IComponent;
                    if (primaryComponent != null)
                    {
                        var designer = _host.GetDesigner(primaryComponent);
                        if (designer != null && designer.Verbs != null && designer.Verbs.Count > 0)
                        {
                            availableVerbs.AddRange(designer.Verbs);
                        }
                    }
                }
                return availableVerbs;
            }
        }

        #endregion

        /// called to invoke menu item verbs
        private void MenuItemClickHandler(object sender, EventArgs e)
        {
            // get the menu item
            var menuItem = sender as MenuItem;
            if (menuItem != null)
            {
                // get and invoke the verb
                var verb = _menuItemVerb[menuItem] as DesignerVerb;
                if (verb != null)
                {
                    try
                    {
                        verb.Invoke();
                    }
                    catch
                    {
                    }
                }
            }
        }

        /// removes all local verbs from the context menu 
        private void ResetContextMenu()
        {
            if (_contextMenu != null && _contextMenu.MenuItems != null && _contextMenu.MenuItems.Count > 0)
            {
                var menuItemArray = new MenuItem[_contextMenu.MenuItems.Count];
                _contextMenu.MenuItems.CopyTo(menuItemArray, 0);
                foreach (var menuItem in menuItemArray)
                {
                    // if its not in the global list, remove it
                    if (!IsInGlobalList(menuItem.Text))
                    {
                        _contextMenu.MenuItems.Remove(menuItem);
                    }
                    // get rid of the menu item from the mapping
                    _menuItemVerb.Remove(menuItem);
                }
            }
        }

/*
        /// removes a local verb
        private void RemoveLocalVerb(DesignerVerb verb)
        {
            if (verb == null)
            {
                throw new ArgumentException("verb");
            }
            // get the associated menuItem 
            MenuItem menuItem = GetMenuItemForVerb(verb);
            if (menuItem != null)
            {
                // undo mapping
                _menuItemVerb.Remove(menuItem);
                // remove from context menu
                _contextMenu.MenuItems.Remove(menuItem);
            }
        }
*/

        /// creats and adds a local verb
        private void CreateAndAddLocalVerb(DesignerVerb verb)
        {
            if (verb == null)
            {
                throw new ArgumentException("verb");
            }
            VerifyVerb(verb);
            // create a menu item for the verb
            var menuItem = new MenuItem(verb.Text);
            // attach the menu item click listener
            menuItem.Click += MenuItemClickHandler;
            // do the menuItem-verb mapping
            _menuItemVerb.Add(menuItem, verb);
            // add to context menu 
            _contextMenu.MenuItems.Add(menuItem);
        }

/*
        /// returns the MenuItem associated with the verb
        private MenuItem GetMenuItemForVerb(DesignerVerb verb)
        {
            MenuItem menuItem = null;
            if (_menuItemVerb != null && _menuItemVerb.Count > 0)
            {
                foreach (DictionaryEntry de in _menuItemVerb)
                {
                    var dv = de.Value as DesignerVerb;
                    if (dv == verb)
                    {
                        menuItem = de.Key as MenuItem;
                        break;
                    }
                }
            }
            return menuItem;
        }
*/

        /// returns true if the verb is in the global verb collection
        private bool IsInGlobalList(string verbText)
        {
            var found = false;
            if (_globalVerbs != null && _globalVerbs.Count > 0)
            {
                foreach (DesignerVerb dv in _globalVerbs)
                {
                    if (string.Compare(dv.Text, verbText, true) == 0)
                    {
                        found = true;
                        break;
                    }
                }
            }
            return found;
        }

        /// we can't add the same verb twice
        private void VerifyVerb(DesignerVerb verb)
        {
            if (verb == null)
            {
                throw new ArgumentException("verb");
            }
            // make sure the verb is not in the global list
            if (_globalVerbs != null && _globalVerbs.Count > 0)
            {
                foreach (DesignerVerb dv in _globalVerbs)
                {
                    if (string.Compare(dv.Text, verb.Text, true) == 0)
                    {
                        throw new Exception("Cannot add the same verb twice.");
                    }
                }
            }
            // now check the _menuItemVerb mapping 
            if (_menuItemVerb != null && _menuItemVerb.Count > 0)
            {
                foreach (DesignerVerb dv in _menuItemVerb.Values)
                {
                    if (string.Compare(dv.Text, verb.Text, true) == 0)
                    {
                        throw new Exception("Cannot add the same verb twice.");
                    }
                }
            }
        }
    }
}